/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkAssignStmt, ArkField, ArkFile, ArkMethod, ArkNewArrayExpr, ArkNewExpr, ClassType, Constant, NumberType, Stmt, ViewTreeNode } from "arkanalyzer/lib";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { ArkClass } from "arkanalyzer/lib/core/model/ArkClass";
import { ClassMatcher, MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { Defects, IssueReport } from "../../model/Defects";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { Rule } from "../../model/Rule";
import { NumberUtils } from "../../utils/checker/NumberUtils";
import { StmtExt } from "../../model/StmtExt";
import { VarInfo } from "../../model/VarInfo";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ObjectCreationCheck');
const gMetaData: BaseMetaData = {
    severity: 3,
    ruleDocPath: "docs/object-creation-check.md",
    description: "We recommend that you use effectKit.createEffect to achieve the blur effect."
};
const regExpName = 'RegExp';
const objectName = 'Object';

export class ObjectCreationCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private clsMatcher: ClassMatcher = {
        matcherType: MatcherTypes.CLASS
    };

    public registerMatchers(): MatcherCallback[] {
        const matchClassCb: MatcherCallback = {
            matcher: this.clsMatcher,
            callback: this.check
        }
        return [matchClassCb];
    }

    public check = (targetCla: ArkClass) => {
        for (let mtd of targetCla.getMethods()) {
            this.processArkMethod(targetCla.getDeclaringArkFile(), mtd);
        }
        for (let field of targetCla.getFields()) {
            this.processArkField(targetCla.getDeclaringArkFile(), field);
        }
    }

    private processArkMethod(arkFile: ArkFile, mtd: ArkMethod) {
        let stmts = mtd.getBody()?.getCfg().getStmts() ?? [];
        for (let stmt of stmts) {
            for (let expr of stmt.getExprs()) {
                if (expr instanceof ArkNewExpr) {
                    let type = expr.getType();
                    if (!(type instanceof ClassType)) {
                        return;
                    }
                    let claName = type.getClassSignature().getClassName();
                    if (claName === regExpName || claName === objectName) {
                        this.reportIssue(arkFile, stmt);
                    }
                } else if (expr instanceof ArkNewArrayExpr) {
                    let size = expr.getSize();
                    let scope = (stmt as StmtExt).scope;
                    let varInfo = new VarInfo(stmt, scope);
                    let count = 0;
                    if (NumberUtils.isValueSupportCalculation(arkFile, varInfo, size)) {
                        count = NumberUtils.getNumberByScope(arkFile, varInfo, size).value;
                    } else {
                        count = 1;
                    }
                    let def = (stmt as ArkAssignStmt).getDef();
                    if (def !== null && count === 0 && !expr.isFromLiteral()) {
                        this.reportIssue(arkFile, stmt);
                    }
                }
            }
        }
    }

    private processArkField(arkFile: ArkFile, field: ArkField) {
        let stmts = field.getInitializer();
        if (stmts.length === 0) {
            return;
        }
        let stmt = stmts[0];
        if (!(stmt instanceof ArkAssignStmt)) {
            return;
        }
        let initialer = stmt.getRightOp();
        if (initialer instanceof ArkNewExpr) {
            let type = initialer.getType();
            if (!(type instanceof ClassType)) {
                return;
            }
            let claName = type.getClassSignature().getClassName();
            if (claName === regExpName || claName === objectName) {
                this.reportIssue(arkFile, field);
            }
        } else if (initialer instanceof ArkNewArrayExpr) {
            let size = initialer.getSize();
            if (size instanceof Constant && size.getType() instanceof NumberType) {
                let valueStr = size.getValue();
                let numberValue = Number(valueStr);
                if (numberValue === 0) {
                    this.reportIssue(arkFile, field);
                }
            }
        }
    }

    private reportIssue(arkFile: ArkFile, stmt: Stmt | ArkField) {
        const severity = this.rule.alert ?? this.metaData.severity;
        const filePath = arkFile.getFilePath();
        const newKey = 'new';
        let lineNum = -1
        let startColumn = -1;
        let endColunm = -1;
        if (stmt instanceof Stmt) {
            const originalPosition = stmt.getOriginPositionInfo();
            lineNum = originalPosition.getLineNo();
            const text = stmt.getOriginalText();
            if (text && text.length !== 0) {
                startColumn = originalPosition.getColNo() + text.indexOf(newKey);
                endColunm = startColumn + newKey.length - 1;
            }
        } else if (stmt instanceof ArkField) {
            lineNum = stmt.getOriginPosition().getLineNo();
            const originaCode = stmt.getCode();
            if (originaCode) {
                startColumn = stmt.getOriginPosition().getColNo() + originaCode.toString().indexOf(newKey);
                endColunm = startColumn + newKey.length - 1;
            }
        }
        const fixKey = lineNum + '%' + startColumn + '%' + endColunm + '%' + this.rule.ruleId;
        const mergeKey = filePath + '%' + fixKey + '%' + this.metaData.description;
        let defects = new Defects(lineNum, startColumn, this.metaData.description, severity, this.rule.ruleId,
            mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey);
        this.issues.push(new IssueReport(defects, undefined));
    }
}